/*!	 asyncrenderer.cpp
**	 Template File
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007 Chris Moore
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "asyncrenderer.h"
#include "app.h"
#include <glibmm/thread.h>
#include <glibmm/dispatcher.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif

#include <synfig/general.h>
#include <synfig/context.h>
#include <ETL/clock>

#include <gui/localization.h>

#endif

using namespace std;
using namespace etl;
using namespace synfig;
using namespace studio;

#define BOREDOM_TIMEOUT		1

#define REJOIN_ON_STOP	1

// The Glib::Dispatcher class is broken as of Glibmm 2.4.5.
// Defining this macro enables the workaround.
#define GLIB_DISPATCHER_BROKEN 1

class AsyncTarget_Tile : public synfig::Target_Tile
{
public:
    etl::handle<synfig::Target_Tile> warm_target;

    struct tile_t {
        Surface surface;
        int x, y;
        tile_t(const Surface& surface, int x, int y):
            surface(surface),
            x(x), y(y)
        {
        }
    };
    std::list<tile_t> tile_queue;
    std::list<Glib::Thread*> threads;
    bool err;
    Glib::Mutex mutex;

#ifndef GLIB_DISPATCHER_BROKEN
    Glib::Dispatcher tile_ready_signal;
#endif
    Glib::Cond cond_tile_queue_empty;
    bool alive_flag;

    sigc::connection ready_connection;

public:
    AsyncTarget_Tile(etl::handle<synfig::Target_Tile> warm_target):
        warm_target(warm_target), err(false)
    {
        set_avoid_time_sync(warm_target->get_avoid_time_sync());
        set_tile_w(warm_target->get_tile_w());
        set_tile_h(warm_target->get_tile_h());
        set_canvas(warm_target->get_canvas());
        set_quality(warm_target->get_quality());
        set_alpha_mode(warm_target->get_alpha_mode());
        set_threads(warm_target->get_threads());
        set_clipping(warm_target->get_clipping());
        set_allow_multithreading(warm_target->get_allow_multithreading());
        set_rend_desc(&warm_target->rend_desc());
        set_engine(warm_target->get_engine());
        alive_flag = true;
#ifndef GLIB_DISPATCHER_BROKEN
        ready_connection = tile_ready_signal.connect(sigc::mem_fun(*this, &AsyncTarget_Tile::tile_ready));
#endif
    }

    ~AsyncTarget_Tile()
    {
        ready_connection.disconnect();
    }
    void set_dead()
    {
        Glib::Mutex::Lock lock(mutex);
        alive_flag = false;
    }

    virtual bool async_render_tile(synfig::RectInt rect, synfig::Context context, synfig::RendDesc tile_desc, synfig::ProgressCallback *cb = NULL)
    {
        if (!alive_flag) {
            return false;
        }

#ifdef SINGLE_THREADED

        if (App::single_threaded) {
            return sync_render_tile(rect, context, tile_desc, cb);
        }

#endif

        if (!get_allow_multithreading()) {
            return sync_render_tile(rect, context, tile_desc, cb);
        }

        Glib::Thread *thread = Glib::Thread::create(
                                   sigc::hide_return(
                                       sigc::bind(
                                           sigc::mem_fun(*this, &AsyncTarget_Tile::sync_render_tile),
                                           rect, context, tile_desc, (synfig::ProgressCallback*)NULL)),
                                   true
                               );
        assert(thread);

        {
            Glib::Mutex::Lock lock(mutex);
            threads.push_back(thread);
        }

        return true;
    }

    bool sync_render_tile(synfig::RectInt rect, synfig::Context context, synfig::RendDesc tile_desc, synfig::ProgressCallback *cb)
    {
        if (!alive_flag) {
            return false;
        }

        bool r = warm_target->async_render_tile(rect, context, tile_desc, cb);

        if (!r) {
            Glib::Mutex::Lock lock(mutex);
            err = true;
        }

        return r;
    }

    virtual bool wait_render_tiles(ProgressCallback *cb = NULL)
    {
        if (!alive_flag) {
            return false;
        }

        while (true) {
            Glib::Thread *thread;
            {
                Glib::Mutex::Lock lock(mutex);

                if (threads.empty()) {
                    break;
                }

                thread = threads.front();
                threads.pop_front();

            }
            thread->join();
        }

        {
            Glib::Mutex::Lock lock(mutex);

            if (err) {
                return false;
            }
        }

        return warm_target->wait_render_tiles(cb);
    }

    virtual int next_tile(RectInt& rect)
    {
        if (!alive_flag) {
            return 0;
        }

        return warm_target->next_tile(rect);
    }

    virtual int next_frame(Time& time)
    {
        if (!alive_flag) {
            return 0;
        }

        return warm_target->next_frame(time);
    }

    virtual bool start_frame(synfig::ProgressCallback *cb = 0)
    {
        if (!alive_flag) {
            return false;
        }

        {
            Glib::Mutex::Lock lock(mutex);
            err = false;
        }

        return warm_target->start_frame(cb);
    }

    virtual bool add_tile(const synfig::Surface &surface, int gx, int gy)
    {
        assert(surface);

        if (!alive_flag) {
            return false;
        }

        Glib::Mutex::Lock lock(mutex);
        tile_queue.push_back(tile_t(surface, gx, gy));

        if (tile_queue.size() == 1) {
#ifdef GLIB_DISPATCHER_BROKEN
            ready_connection = Glib::signal_timeout().connect(
                                   sigc::bind_return(
                                       sigc::mem_fun(*this, &AsyncTarget_Tile::tile_ready),
                                       false
                                   )
                                   , 0
                               );
#else
            tile_ready_signal();
#endif
        }

        return alive_flag;
    }

    void tile_ready()
    {
        Glib::Mutex::Lock lock(mutex);

        if (!alive_flag) {
            tile_queue.clear();
            cond_tile_queue_empty.signal();
            return;
        }

        while (!tile_queue.empty() && alive_flag) {
            tile_t& tile(tile_queue.front());

            if (getenv("SYNFIG_SHOW_TILE_OUTLINES")) {
                Color red(1, 0, 0);
                tile.surface.fill(red, 0, 0, 1, tile.surface.get_h());
                tile.surface.fill(red, 0, 0, tile.surface.get_w(), 1);
            }

            alive_flag = warm_target->add_tile(tile.surface, tile.x, tile.y);

            tile_queue.pop_front();
        }

        cond_tile_queue_empty.signal();
    }

    virtual void end_frame()
    {
#ifdef SINGLE_THREADED

        if (!App::single_threaded) {
#endif

            while (alive_flag) {
                Glib::Mutex::Lock lock(mutex);
                Glib::TimeVal end_time;

                end_time.assign_current_time();
                end_time.add_microseconds(BOREDOM_TIMEOUT);

                if (!tile_queue.empty() && alive_flag) {
                    if (cond_tile_queue_empty.timed_wait(mutex, end_time)) {
                        break;
                    }
                } else {
                    break;
                }
            }

#ifdef SINGLE_THREADED
        }

#endif
        Glib::Mutex::Lock lock(mutex);

        if (!alive_flag) {
            return;
        }

        return warm_target->end_frame();
    }
};

class AsyncTarget_Cairo_Tile : public synfig::Target_Cairo_Tile
{
public:
    etl::handle<synfig::Target_Cairo_Tile> warm_target;

    class tile_t
    {
    public:
        cairo_surface_t* surface;
        int x, y;
        tile_t(): surface(NULL), x(0), y(0)
        {
        }
        tile_t(cairo_surface_t*& surface_, int x_, int y_)
        {
            if (surface_) {
                surface = cairo_surface_reference(surface_);
            } else {
                surface = surface_;
            }

            x = x_;
            y = y_;
        }
        tile_t(const tile_t &other):
            surface(cairo_surface_reference(other.surface)),
            x(other.x),
            y(other.y)
        {
        }
        ~tile_t()
        {
            if (surface) {
                cairo_surface_destroy(surface);
            }
        }
    };
    std::list<tile_t> tile_queue;
    Glib::Mutex mutex;

#ifndef GLIB_DISPATCHER_BROKEN
    Glib::Dispatcher tile_ready_signal;
#endif
    Glib::Cond cond_tile_queue_empty;
    bool alive_flag;

    sigc::connection ready_connection;

public:
    AsyncTarget_Cairo_Tile(etl::handle<synfig::Target_Cairo_Tile> warm_target):
        warm_target(warm_target)
    {
        set_avoid_time_sync(warm_target->get_avoid_time_sync());
        set_tile_w(warm_target->get_tile_w());
        set_tile_h(warm_target->get_tile_h());
        set_canvas(warm_target->get_canvas());
        set_quality(warm_target->get_quality());
        set_alpha_mode(warm_target->get_alpha_mode());
        set_threads(warm_target->get_threads());
        set_clipping(warm_target->get_clipping());
        set_rend_desc(&warm_target->rend_desc());
        alive_flag = true;
#ifndef GLIB_DISPATCHER_BROKEN
        ready_connection = tile_ready_signal.connect(sigc::mem_fun(*this, &AsyncTarget_Cairo_Tile::tile_ready));
#endif
    }

    ~AsyncTarget_Cairo_Tile()
    {
        ready_connection.disconnect();
    }
    void set_dead()
    {
        Glib::Mutex::Lock lock(mutex);
        alive_flag = false;
    }

    virtual int next_tile(int& x, int& y)
    {
        if (!alive_flag) {
            return 0;
        }

        return warm_target->next_tile(x, y);
    }

    virtual int next_frame(Time& time)
    {
        if (!alive_flag) {
            return 0;
        }

        return warm_target->next_frame(time);
    }

    virtual bool start_frame(synfig::ProgressCallback *cb = 0)
    {
        if (!alive_flag) {
            return false;
        }

        return warm_target->start_frame(cb);
    }

    virtual bool add_tile(cairo_surface_t* surface, int gx, int gy)
    {
        if (cairo_surface_status(surface)) {
            return false;
        }

        if (!alive_flag) {
            return false;
        }

        Glib::Mutex::Lock lock(mutex);
        tile_queue.push_back(tile_t(surface, gx, gy));

        if (tile_queue.size() == 1) {
#ifdef GLIB_DISPATCHER_BROKEN
            ready_connection = Glib::signal_timeout().connect(
                                   sigc::bind_return(
                                       sigc::mem_fun(*this, &AsyncTarget_Cairo_Tile::tile_ready),
                                       false
                                   )
                                   , 0
                               );
#else
            tile_ready_signal();
#endif
        }

        return alive_flag;
    }

    void tile_ready()
    {
        Glib::Mutex::Lock lock(mutex);

        if (!alive_flag) {
            tile_queue.clear();
            cond_tile_queue_empty.signal();
            return;
        }

        while (!tile_queue.empty() && alive_flag) {
            tile_t& tile(tile_queue.front());

//				tile.surface.fill(red, 0, 0, 1, tile.surface.get_h());
//				tile.surface.fill(red, 0, 0, tile.surface.get_w(), 1);

            alive_flag = warm_target->add_tile(tile.surface, tile.x, tile.y);

            tile_queue.pop_front();
        }

        cond_tile_queue_empty.signal();
    }

    virtual void end_frame()
    {
#ifdef SINGLE_THREADED

        if (!App::single_threaded) {
#endif

            while (alive_flag) {
                Glib::Mutex::Lock lock(mutex);
                Glib::TimeVal end_time;

                end_time.assign_current_time();
                end_time.add_microseconds(BOREDOM_TIMEOUT);

                if (!tile_queue.empty() && alive_flag) {
                    if (cond_tile_queue_empty.timed_wait(mutex, end_time)) {
                        break;
                    }
                } else {
                    break;
                }
            }

#ifdef SINGLE_THREADED
        }

#endif
        Glib::Mutex::Lock lock(mutex);

        if (!alive_flag) {
            return;
        }

        return warm_target->end_frame();
    }
};

class AsyncTarget_Scanline : public synfig::Target_Scanline
{
public:
    etl::handle<synfig::Target_Scanline> warm_target;

    int scanline_;
    Surface surface;

    Glib::Mutex mutex;

#ifndef GLIB_DISPATCHER_BROKEN
    Glib::Dispatcher frame_ready_signal;
#endif
    Glib::Cond cond_frame_queue_empty;
    bool alive_flag;
    bool ready_next;
    sigc::connection ready_connection;

public:
    AsyncTarget_Scanline(etl::handle<synfig::Target_Scanline> warm_target):
        warm_target(warm_target),
        scanline_(),
        alive_flag(),
        ready_next()
    {
        set_avoid_time_sync(warm_target->get_avoid_time_sync());
        set_canvas(warm_target->get_canvas());
        set_quality(warm_target->get_quality());
        set_alpha_mode(warm_target->get_alpha_mode());
        set_threads(warm_target->get_threads());
        set_rend_desc(&warm_target->rend_desc());
        alive_flag = true;
#ifndef GLIB_DISPATCHER_BROKEN
        ready_connection = frame_ready_signal.connect(sigc::mem_fun(*this, &AsyncTarget_Scanline::frame_ready));
#endif
        surface.set_wh(warm_target->rend_desc().get_w(), warm_target->rend_desc().get_h());
    }

    ~AsyncTarget_Scanline()
    {
        ready_connection.disconnect();
    }

    virtual int next_frame(Time& time)
    {
        if (!alive_flag) {
            return 0;
        }

        return warm_target->next_frame(time);

    }

    void set_dead()
    {
        Glib::Mutex::Lock lock(mutex);
        alive_flag = false;
    }

    virtual bool start_frame(synfig::ProgressCallback */*cb*/ = 0)
    {
        return alive_flag;
    }

    virtual void end_frame()
    {
        {
            Glib::Mutex::Lock lock(mutex);

            if (!alive_flag) {
                return;
            }

            ready_next = false;

#ifdef GLIB_DISPATCHER_BROKEN
            ready_connection = Glib::signal_timeout().connect(
                                   sigc::bind_return(
                                       sigc::mem_fun(*this, &AsyncTarget_Scanline::frame_ready),
                                       false
                                   )
                                   , 0
                               );
#else
            frame_ready_signal();
#endif
        }

#ifdef SINGLE_THREADED

        if (App::single_threaded) {
            signal_progress()();
        } else
#endif
        {

            Glib::TimeVal end_time;

            end_time.assign_current_time();
            end_time.add_microseconds(BOREDOM_TIMEOUT);

            while (alive_flag && !ready_next) {
                Glib::Mutex::Lock lock(mutex);

                if (cond_frame_queue_empty.timed_wait(mutex, end_time)) {
                    break;
                }
            }
        }
    }

    virtual Color * start_scanline(int scanline)
    {
        Glib::Mutex::Lock lock(mutex);

        return surface[scanline];
    }

    virtual bool end_scanline()
    {
        return alive_flag;
    }

    void frame_ready()
    {
        Glib::Mutex::Lock lock(mutex);

        if (alive_flag) {
            alive_flag = warm_target->add_frame(&surface);
        }

#ifdef SINGLE_THREADED

        if (!App::single_threaded)
#endif
            cond_frame_queue_empty.signal();

        ready_next = true;
    }
};

class AsyncTarget_Cairo : public synfig::Target_Cairo
{
public:
    etl::handle<synfig::Target_Cairo> warm_target;

    cairo_surface_t* surface;
    ProgressCallback *callback;

    Glib::Mutex mutex;

#ifndef GLIB_DISPATCHER_BROKEN
    Glib::Dispatcher frame_ready_signal;
#endif
    Glib::Cond cond_frame_queue_empty;
    bool alive_flag;
    bool ready_next;
    sigc::connection ready_connection;


public:
    AsyncTarget_Cairo(etl::handle<synfig::Target_Cairo> warm_target):
        warm_target(warm_target),
        surface(),
        callback(),
        alive_flag(),
        ready_next()
    {
        set_avoid_time_sync(warm_target->get_avoid_time_sync());
        set_canvas(warm_target->get_canvas());
        set_quality(warm_target->get_quality());
        set_alpha_mode(warm_target->get_alpha_mode());
        set_rend_desc(&warm_target->rend_desc());
        alive_flag = true;
#ifndef GLIB_DISPATCHER_BROKEN
        ready_connection = frame_ready_signal.connect(sigc::mem_fun(*this, &AsyncTarget_Cairo::frame_ready));
#endif
    }

    ~AsyncTarget_Cairo()
    {
        ready_connection.disconnect();
    }

    virtual int next_frame(Time& time)
    {
        Glib::Mutex::Lock lock(mutex);

        if (!alive_flag) {
            return 0;
        }

        return warm_target->next_frame(time);

    }

    void set_dead()
    {
        Glib::Mutex::Lock lock(mutex);
        alive_flag = false;
    }

    virtual bool put_surface(cairo_surface_t* s, ProgressCallback *cb)
    {
        {
            Glib::Mutex::Lock lock(mutex);
            surface = s;
            callback = cb;

            if (!alive_flag) {
                return false;
            }

            ready_next = false;

#ifdef GLIB_DISPATCHER_BROKEN
            ready_connection = Glib::signal_timeout().connect(
                                   sigc::bind_return(sigc::mem_fun(*this, &AsyncTarget_Cairo::frame_ready), false)
                                   , 0
                               );
#else
            frame_ready_signal();
#endif
        }

#ifdef SINGLE_THREADED

        if (App::single_threaded) {
            signal_progress()();
        } else
#endif
        {

            Glib::TimeVal end_time;

            end_time.assign_current_time();
            end_time.add_microseconds(BOREDOM_TIMEOUT);

            while (alive_flag && !ready_next) {
                Glib::Mutex::Lock lock(mutex);

                if (cond_frame_queue_empty.timed_wait(mutex, end_time)) {
                    break;
                }
            }
        }

        return true;
    }

    void frame_ready()
    {
        Glib::Mutex::Lock lock(mutex);

        if (alive_flag) {
            alive_flag = warm_target->put_surface(surface, callback);
        }

#ifdef SINGLE_THREADED

        if (!App::single_threaded)
#endif
            cond_frame_queue_empty.signal();

        ready_next = true;
    }

    virtual bool obtain_surface(cairo_surface_t*& s)
    {
        Glib::Mutex::Lock lock(mutex);

        if (!alive_flag) {
            return false;
        }

        return warm_target->obtain_surface(s);
    }

};

AsyncRenderer::AsyncRenderer(etl::handle<synfig::Target> target_, synfig::ProgressCallback *cb):
    error(false),
    success(false),
    cb(cb),
#ifdef SINGLE_THREADED
    updating(false),
#endif
    start_clock(0),
    finish_clock(0),
    start_time(0, 0),
    finish_time(0, 0)
{
    render_thread = 0;

    if (etl::handle<synfig::Target_Tile>::cast_dynamic(target_)) {
        etl::handle<AsyncTarget_Tile> wrap_target(
            new AsyncTarget_Tile(etl::handle<synfig::Target_Tile>::cast_dynamic(target_))
        );

        signal_stop_.connect(sigc::mem_fun(*wrap_target, &AsyncTarget_Tile::set_dead));

        target = wrap_target;
    } else if (etl::handle<synfig::Target_Scanline>::cast_dynamic(target_)) {
        etl::handle<AsyncTarget_Scanline> wrap_target(
            new AsyncTarget_Scanline(
                etl::handle<synfig::Target_Scanline>::cast_dynamic(target_)
            )
        );

        signal_stop_.connect(sigc::mem_fun(*wrap_target, &AsyncTarget_Scanline::set_dead));

        target = wrap_target;
    } else if (etl::handle<synfig::Target_Cairo_Tile>::cast_dynamic(target_)) {
        etl::handle<AsyncTarget_Cairo_Tile> wrap_target(
            new AsyncTarget_Cairo_Tile(
                etl::handle<synfig::Target_Cairo_Tile>::cast_dynamic(target_)
            )
        );

        signal_stop_.connect(sigc::mem_fun(*wrap_target, &AsyncTarget_Cairo_Tile::set_dead));

        target = wrap_target;
    } else if (etl::handle<synfig::Target_Cairo>::cast_dynamic(target_)) {
        etl::handle<AsyncTarget_Cairo> wrap_target(
            new AsyncTarget_Cairo(
                etl::handle<synfig::Target_Cairo>::cast_dynamic(target_)
            )
        );

        signal_stop_.connect(sigc::mem_fun(*wrap_target, &AsyncTarget_Cairo::set_dead));

        target = wrap_target;
    }
}

AsyncRenderer::~AsyncRenderer()
{
    stop();
}

void
AsyncRenderer::stop()
{
    if (target) {
        Glib::Mutex::Lock lock(mutex);
        done_connection.disconnect();

        if (render_thread) {
            signal_stop_();

#if REJOIN_ON_STOP
#ifdef SINGLE_THREADED

            if (!App::single_threaded)
#endif
                render_thread->join();

#endif
            finish_time.assign_current_time();
            finish_clock = ::clock();

            // Make sure all the dispatch crap is cleared out

            if (success) {
                signal_success_();
            }

            target = 0;
            render_thread = 0;

            lock.release();

            signal_finished_();
        }
    }
}

void
AsyncRenderer::pause()
{
}

void
AsyncRenderer::resume()
{
}

void
AsyncRenderer::start()
{
    start_time.assign_current_time();
    finish_time = start_time;
    start_clock = ::clock();
    finish_clock = start_clock;
    done_connection = Glib::signal_timeout().connect(
                          sigc::bind_return(
                              mem_fun(*this, &AsyncRenderer::start_),
                              false
                          )
                          , 0
                      );
}

#ifdef SINGLE_THREADED
void
AsyncRenderer::rendering_progress()
{
    updating = true;

    while (studio::App::events_pending()) {
        studio::App::iteration(false);
    }

    updating = false;
}
#endif

void
AsyncRenderer::start_()
{
    error = false;
    success = false;

    if (target) {
#ifndef GLIB_DISPATCHER_BROKEN
        done_connection = signal_done_.connect(mem_fun(*this, &AsyncRenderer::stop));
#endif

#ifdef SINGLE_THREADED

        if (App::single_threaded) {
            target->signal_progress().connect(sigc::mem_fun(this, &AsyncRenderer::rendering_progress));
            render_thread = (Glib::Thread*)1;
            render_target();
        } else
#endif
        {
            render_thread = Glib::Thread::create(
                                sigc::mem_fun(*this, &AsyncRenderer::render_target),
#if REJOIN_ON_STOP
                                true
#else
                                false
#endif
                            );
            assert(render_thread);
        }
    } else {
        stop();
    }
}

void
AsyncRenderer::render_target()
{
    etl::handle<Target> target(AsyncRenderer::target);

    if (target && target->render()) {
        success = true;
    } else {
        error = true;
#ifndef REJOIN_ON_STOP
        return;
#endif
    }

    if (mutex.trylock()) {
#ifdef GLIB_DISPATCHER_BROKEN
        done_connection = Glib::signal_timeout().connect(
                              sigc::bind_return(
                                  mem_fun(*this, &AsyncRenderer::stop),
                                  false
                              )
                              , 0
                          );
#else
        signal_done_.emit();
#endif
        mutex.unlock();
    }
}